package com.baiyang.server.config;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.AuthenticatedVoter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.expression.WebExpressionVoter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.alibaba.fastjson.JSONObject;
import com.baiyang.server.config.provider.MyAuthenticationProvider;
import com.baiyang.server.config.provider.MyWebAuthenticationDetailsSource;
import com.baiyang.server.config.provider.UserDetail;
import com.baiyang.server.filter.MyUsernamePasswordAuthenticationFilter;
import com.baiyang.server.model.system.User;
import com.baiyang.server.tools.Data;
import com.baiyang.server.tools.Letter;


/**
 * @author XVX
 * spring security的配置类，配置security相关属性和调用顺序
 * 
 * 在使用配置类的过程中：需要继承WebSecurityConfigurerAdapter，并注解@EnableGlobalMethodSecurity
 * (securedEnabled = true) --- 允许使用spring security注解 
 */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MySecurityConf extends WebSecurityConfigurerAdapter{
	
	@Autowired
	private MyAuthenticationProvider authenticationProvider = null;
	@Autowired
	private MyWebAuthenticationDetailsSource authenticationDetailsSource = null;
	/**
	 * 通过该配置可以设置AuthenticationManager执行的AuthenticationProvider
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// TODO Auto-generated method stub
//		super.configure(auth);
		auth.authenticationProvider(authenticationProvider);

	}
	/**
	 * 需要重写该方法实现自己的认证过程
	 * HttpSecurity讲解
	 * ---------------------------------------------
	 * authorizeRequests()方法将返回一个URL拦截器，并可以通过相应的方法来匹配相关的URL并配置安全策略。
	 * HttpSecurity实际上对用了spring security的XML文件中的标签，也就是说可以通过HttpSecurity实现XML配置，当程序启动的过程中会读取该配置文件
	 * HttpSecurity使用了链式调用方法，在执行每个方法后，都会返回一个预期的上下文，便于连续调用。
	 * ---------------------------------------------
	 * authenticationDetailsSource()方法设置了 @code(AbstractAuthenticationFilterConfigurer)类中的authenticationDetailsSource成员变量
	  * 在实际运行过程中UsernamePasswordAuthenticationFilter类型的过滤器的authenticationDetailsSource对象是通过AbstractAuthenticationFilterConfigurer类获取的
	 * bug:在同时调用addFilterAt()方法和authenticationDetailsSource()方法时，本质上authenticationDetailsSource()方法是对UsernamePasswordAuthenticationFilter类进行创建对象并且赋值的，因此自定义的UsernamePasswordAuthenticationFilter子类不能
	 * 重新设定authenticationDetailsSource成员对象
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.addFilterAt(getFilter(), UsernamePasswordAuthenticationFilter.class).
		authorizeRequests().
//		antMatchers("/userOperation/insert","/userOperation/getVerPic").permitAll().
		anyRequest().authenticated().and().
		authorizeRequests().accessDecisionManager(accessDecisionManager()).and().csrf()
				.disable().exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {

					@Override
					public void commence(HttpServletRequest request, HttpServletResponse response,
							AuthenticationException authException) throws IOException, ServletException {
						// TODO Auto-generated method stub
						System.out.println("调用authenticationEntryPoint：授权失败！");
						response.setStatus(403);
					}
				}).and().logout().logoutUrl("/logout").logoutSuccessHandler((req, res, authentication) -> {

				});
	}
	
	private MyUsernamePasswordAuthenticationFilter getFilter() throws Exception {
		MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter();
		filter.setAuthenticationManager(super.authenticationManagerBean());
		filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
			// 登录认证失败时操作
			@Override
			public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
					AuthenticationException exception) throws IOException, ServletException {
				// TODO Auto-generated method stub
				System.out.println("调用onAuthenticationFailure:认证过程失败！" + exception.getMessage());
				Letter<User> letter = new Letter<User>();
				if(exception.getMessage().toString().equals(Data.ERROR.VERCODE)) {
					letter.setResult("FAIL");
					letter.setDetail("验证码错误！");
				}else {
					letter.setResult("FAIL");
					letter.setDetail("账号密码异常！");
				}
				response.getOutputStream().write(JSONObject.toJSONString(letter).getBytes());
				response.getOutputStream().flush();
			}
		});
		filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
			// 登录认证成功时操作
			@Override
			public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
					Authentication authentication) throws IOException, ServletException {
				// TODO Auto-generated method stub
				System.out.println("调用onAuthenticationSuccess:认证过程成功！");
				Letter<User> letter = new Letter<User>();
				letter.setResult("SUCCESS");
				letter.setDetail("登录成功");
				User user = ((UserDetail) authentication.getPrincipal()).getUser();
				user.setPassword("");
				letter.setData(user);
				response.getOutputStream().write(JSONObject.toJSONString(letter).getBytes());
				response.getOutputStream().flush();
				request.getSession().setAttribute(Data.Session.USER, user);
			}
		});
		filter.setAuthenticationDetailsSource(authenticationDetailsSource);
		return filter;
	}
	
	/**
	 * AccessDecisionManager -- 投票处理器（AbstractAccessDecisionManager）
	 * AccessDecisionVoter   -- 投票参与者
	 * @return
	 * 
	 * FilterSecurityInterceptor 过滤器进行身份验证
	 * 
	 * AffirmativeBased – 任何一个AccessDecisionVoter返回同意则允许访问
     * ConsensusBased – 同意投票多于拒绝投票（忽略弃权回答）则允许访问
     * UnanimousBased – 每个投票者选择弃权或同意则允许访问
	 */
	 public AccessDecisionManager accessDecisionManager() {
		 List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList(
//	            new WebExpressionVoter(),
	            // new RoleVoter(),
//	            new AuthenticatedVoter(),
	            new RoleBasedVoter());
		 return new AffirmativeBased(decisionVoters);
	 }
	 
	  /**
	   * 密码生成策略.
	   *
	   * @return
	   */
	  @Bean
	  public PasswordEncoder passwordEncoder() {
	    return new BCryptPasswordEncoder();
	  }
}
